package com.hwtx.form.domain.def.meta.config;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hwtx.form.domain.def.FormConfigExt;
import com.hwtx.form.domain.def.meta.Api;
import com.hwtx.form.domain.def.meta.Component;
import com.hwtx.form.domain.handle.datamodel.DataModel;
import com.hwtx.form.domain.handle.datamodel.DataModelDetail;
import com.hwtx.form.domain.repo.ApiRepo;
import com.hwtx.form.domain.service.CommonApiInvoker;
import com.hwtx.form.domain.vo.FormData;
import com.hwtx.form.persistence.entity.ApiDef;
import com.hwtx.form.persistence.entity.ApiDefField;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.JacksonUtil;
import io.geekidea.boot.config.properties.BootProperties;
import io.geekidea.boot.framework.exception.BusinessException;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.boot.autoconfigure.web.ServerProperties;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.domain.def.meta.ChainedSelect.*;
import static com.hwtx.form.domain.handle.datamodel.DataModel.isSqlModel;
import static com.hwtx.form.util.FormUtil.*;

@org.springframework.stereotype.Component
public class ChainSelectFormComponentConfig implements FormComponentConfig {
    public static final String ModelSource = "modelSource";
    public static final String ChainSelectType = "chainSelectType";

    @Resource
    private ServerProperties webApplicationContext;
    @Resource
    private ApiRepo apiRepo;
    @Resource
    private BootProperties bootProperties;

    @Override
    public ComponentParamInfo getInvokeComponentParamInfo(Map<String, Object> comExt) {
        ComponentParamInfo componentParamInfo = new ComponentParamInfo();
        componentParamInfo.setCode(comExt.get(SOURCE).toString());
        List<ApiDefField> outputs = apiRepo.getApiDefFields(ApiRepo.ApiDefFieldQuery.builder().apiCode(componentParamInfo.getCode()).build());
        componentParamInfo.setOutput(outputs.stream().map(field -> {
            ParamMapping paramMapping = new ParamMapping();
            paramMapping.setKey(field.getName());
            return paramMapping;
        }).toList());
        ApiDef apiDef = apiRepo.getApiDef(componentParamInfo.getCode());
        componentParamInfo.setInput(CommonApiInvoker.getInputParamNames(apiDef).stream().map(field -> {
            ParamMapping paramMapping = new ParamMapping();
            paramMapping.setKey(field);
            return paramMapping;
        }).toList());
        return componentParamInfo;
    }

    @Override
    public Map<String, Object> comExt(Map<String, Object> formData) {
        return getComponentParamInfo(formData);
    }

    @Override
    public Map<String, List<String>> getFields(Map<String, Object> ext) {
        if (ext == null) {
            return Maps.newHashMap();
        }
        Object fields = ext.get(FIELDS);
        if (fields != null) {
            String table = DEFAULT_TABLE_NAME;
            Object fTable = ext.get(F_TABLE);
            if (fTable != null) {
                table = fTable.toString();
            }
            return Map.of(table, List.of(fields.toString().split(",")));
        }
        return Maps.newHashMap();
    }

    @Override
    public Map<String, Object> getInputConfigParams(Map<String, Object> comExt, FormData formData) {
        comExt.putAll(getFieldsData(formData.getData()));
        String type = getStrPostValue(formData.getData(), ChainSelectType);
        String table = getStrPostValue(formData.getData(), "tableName");
        if (StringUtils.isNotEmpty(table)) {
            comExt.put(F_TABLE, table);
        }
        comExt.put(ChainSelectType, type);
        if (Objects.equals(type, "1")) {
            comExt.remove(ModelSource);
        } else if (Objects.equals(type, "2")) {
            comExt.remove(FIELDS);
        }
        FormComponentConfig.fillInputSource(formData, comExt);
        return comExt;
    }

    @Override
    public OutputConfig getOutputConfigParams(String type, FormConfigExt.FormConfigCtr formConfigCtr) {
        if (formConfigCtr == null) {
            return null;
        }
        Map<String, Object> ret = formConfigCtr.getComExt();
        if (Objects.equals(type, QUERY)) {
            ret.putAll(getParamInfoCode(formConfigCtr, Map.of(ModelSource, true, SOURCE, false)));
        }
        return OutputConfig.builder().params(ret).notIncludeKeys(List.of("comExt")).build();
    }

    @Override
    public String getConfig(String type, Map<String, Object> formData) {
        String modelName = FormUtil.getStrPostValue(formData, "modelName");
        String domain = bootProperties.getDomain();

        if (formData.containsKey(ModelSource)) {
            List<Component> formBody = Lists.newArrayList();
            String modelSource = getStrPostValue(formData, ModelSource);
            if (StringUtils.isNotBlank(modelSource)) {
                formBody.addAll(getDataModelParamsConfig(modelSource, formData));
            }
            return buildInputOutputConfig(domain, formBody, FormUtil.getUrlContext(webApplicationContext));
        }
        String urlContext = getUrlContext(webApplicationContext);
        Select select = FormComponentConfig.getSelect(ChainSelectType, "类型", "类型", null,
                List.of(new com.hwtx.form.domain.def.Select.Option("字段类型", "1"),
                        new com.hwtx.form.domain.def.Select.Option("模型类型", "2")));
        select.setFormBatProp();
        Select modelSource = FormComponentConfig.getApiSelect(domain, "模型", "选择模型", ModelSource, ModelSource, "${event.data." + ModelSource + "}",
                Api.builder().method(HTTP_GET).url(domain + urlContext + "/common/api/data-model/2").build(), urlContext);
        modelSource.setFormBatProp();
        modelSource.setVisibleOn("${" + ChainSelectType + " == 2}");
        Select source = FormComponentConfig.getSourceConfig(domain, "#i18n{source}", urlContext);
        source.setVisibleOn("${" + ChainSelectType + " == 2}");
        source.setFormBatProp();
        return JacksonUtil.object2json(List.of(select, modelSource, source, buildFields(domain, modelName, urlContext)));
    }

    private List<Component> buildFields(String domain, String modelName, String urlContext) {
        List<? extends Select> fieldsSelect = FormComponentConfig.getFieldSelect(domain, modelName, urlContext);
        Group group = new Group();
        List<Component> controls = Lists.newArrayList();
        if (fieldsSelect.size() < 2) {
            Component.Select select = fieldsSelect.getFirst();
            select.setColumnRatio(7);
            controls.add(select);
            select = FormComponentConfig.getSourceConfig(domain, null, urlContext);
            select.setColumnRatio(5);
            controls.add(select);
            group.setBody(controls);
            group.setVisibleOn("${" + ChainSelectType + " == 1}");
            return List.of(group);
        } else {
            controls.addAll(fieldsSelect);
            group.setBody(controls);
            group.setVisibleOn("${" + ChainSelectType + " == 1}");
            Select select = FormComponentConfig.getSourceConfig(domain, null, urlContext);
            select.setVisibleOn("${" + ChainSelectType + " == 1}");
            return List.of(group, select);
        }
    }

    @Override
    public ComponentDataModelParamInfo getDataModelParamInfo(Map<String, Object> comExt) {
        ComponentParamInfo componentParamInfo = getParamInfo(ModelSource, comExt);
        if (componentParamInfo != null) {
            ComponentDataModelParamInfo modelParamInfo = new ComponentDataModelParamInfo();
            modelParamInfo.setModelName(componentParamInfo.getCode());
            modelParamInfo.setInput(componentParamInfo.getInput());
            modelParamInfo.setOutput(componentParamInfo.getOutput());
            if (comExt.get(F_TABLE) != null) {
                modelParamInfo.setTable(comExt.get(F_TABLE).toString());
            }
            return modelParamInfo;
        }
        return null;
    }

    private List<Component> getDataModelParamsConfig(String modelName, Map<String, Object> formData) {
        return buildParamForm(modelName, ChainSelectFormComponentConfig.ModelSource, formData, apiCode -> {
            DataModel dataModel = getDataModel(modelName);
            if (!isSqlModel(dataModel.getModelType())) {
                throw new BusinessException("数据模型非SQL类型");
            }
            List<String> inputParams = extractSqlVariables(dataModel.getSqlContent());
            return Pair.of(inputParams, getDataModelDetails(modelName).stream().map(DataModelDetail::getName).toList());
        });
    }
}